package algorithm7.leetcode.hot100.c2双指针;

import org.junit.jupiter.api.Test;

import java.util.*;

/**
 * @author merrick.peng
 */
public class h6_LC15三数之和 {
//1
    public List<List<Integer>> threeSum(int[] nums) {
//        return handle1(nums);
        return handle2(nums);
    }

    private List<List<Integer>> handle2(int[] nums) {
        List<List<Integer>> res = new ArrayList<>();
        // 排序
        Arrays.sort(nums); // 要找 a + b + c = 0 且不重复；通过排序,a < b < c【相邻两个不能相等,避免重复】,只会找到abc 不会找到bac,cba...所以不会重复
        int len = nums.length;
        // 先取一个数，转为两数之和
        for (int picked = 0; picked < len; picked++) {
            if (nums[picked] > 0) break; // 由于数组已排序，如果当前数大于0，则不可能存在和为0的三元组
            if (picked > 0 && nums[picked] == nums[picked - 1]) continue; // 和前面的数相等，说明有重复；直接跳过
            // 求两数之和（l为左指针，r为右指针）
            // 数组有序,r必定在l的右边；所以l从左边找,r从右边找，使用双指针可以去掉一重循环
            int l = picked + 1, r = len - 1;
            while (l < r) {
                int sum = nums[picked] + nums[l] + nums[r];
                if (sum == 0) {
                    // 和为0，找到一个三元组
                    while (l < r && nums[l] == nums[l + 1]) {
                        // 避免重复
                        l++;
                    }
                    while (l < r && nums[r] == nums[r - 1]) {
                        // 避免重复
                        r--;
                    }
                    res.add(Arrays.asList(nums[picked], nums[l], nums[r]));
                    l++;
                    r--;
                }
                if (sum < 0) {
                    // r左移只会让sum更小，所以l右移使sum更趋近0
                    l++;
                }
                if (sum > 0) {
                    r--;
                }
            }
        }
        return res;
    }

    /**
     * 暴力解(超时)
     */
    private List<List<Integer>> handle1(int[] nums) {
        Set<List<Integer>> res = new HashSet<>();
        int n = nums.length;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                for (int k = 0; k < n; k++) {
                    if (i != j && i != k && j != k && nums[i] + nums[j] + nums[k] == 0) {
                        List<Integer> list = Arrays.asList(nums[i], nums[j], nums[k]);
                        list.sort(Comparator.comparingInt(Integer::intValue));
                        res.add(list);
                    }
                }
            }
        }
        return new ArrayList<>(res);
    }

    /*
https://leetcode.cn/problems/3sum/?envType=study-plan-v2&envId=top-100-liked
15. 三数之和
给你一个整数数组 nums ，判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ，同时还满足 nums[i] + nums[j] + nums[k] == 0 。请
你返回所有和为 0 且不重复的三元组。
注意：答案中不可以包含重复的三元组。

示例 1：
输入：nums = [-1,0,1,2,-1,-4]
输出：[[-1,-1,2],[-1,0,1]]
解释：
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意，输出的顺序和三元组的顺序并不重要。

示例 2：
输入：nums = [0,1,1]
输出：[]
解释：唯一可能的三元组和不为 0 。

示例 3：
输入：nums = [0,0,0]
输出：[[0,0,0]]
解释：唯一可能的三元组和为 0 。

     */

    @Test
    void t() {
        int[] nums = {-1, 0, 1, 2, -1, -4};
        List<List<Integer>> res = threeSum(nums);
        System.out.println(res);
    }

}
